package com.sunday.authorization.mock.oauth2.server.config;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;

/**
 * 访问地址 : http://localhost:8080/.well-known/oauth-authorization-server
 * {
 *     "issuer": "http://localhost:8080",
 *     "authorization_endpoint": "http://localhost:8080/oauth2/authorize",                      // 客户端请求授权得接口，这个要在授权码模式才会用到
 *     "device_authorization_endpoint": "http://localhost:8080/oauth2/device_authorization",    // 在设备授权流程中用来提交授权请求，允许在没有浏览器的设备上进行用户认证和授权。
 *     "token_endpoint": "http://localhost:8080/oauth2/token",                                  // 获取Token接口
 *     "token_endpoint_auth_methods_supported": [                                               // 客户端得认证方式
 *         "client_secret_basic",
 *         "client_secret_post",
 *         "client_secret_jwt",
 *         "private_key_jwt"
 *     ],
 *     "jwks_uri": "http://localhost:8080/oauth2/jwks",                                         // 公钥地址，这个地址一般是给资源服务器用的，用来验证token是否有效
 *     "response_types_supported": [
 *         "code"
 *     ],
 *     "grant_types_supported": [                                                              // 支持的授权方式
 *         "authorization_code",
 *         "client_credentials",
 *         "refresh_token",
 *         "urn:ietf:params:oauth:grant-type:device_code"
 *     ],
 *     "revocation_endpoint": "http://localhost:8080/oauth2/revoke",
 *     "revocation_endpoint_auth_methods_supported": [
 *         "client_secret_basic",
 *         "client_secret_post",
 *         "client_secret_jwt",
 *         "private_key_jwt"
 *     ],
 *     "introspection_endpoint": "http://localhost:8080/oauth2/introspect",
 *     "introspection_endpoint_auth_methods_supported": [
 *         "client_secret_basic",
 *         "client_secret_post",
 *         "client_secret_jwt",
 *         "private_key_jwt"
 *     ],
 *     "code_challenge_methods_supported": [
 *         "S256"
 *     ]
 * }
 *
 *
 * 判定的过滤器
 * 0 = {DisableEncodeUrlFilter@9213}
 * 1 = {WebAsyncManagerIntegrationFilter@9212}
 * 2 = {SecurityContextHolderFilter@9211}
 * 3 = {AuthorizationServerContextFilter@7655}
 * 4 = {HeaderWriterFilter@7654}
 * 5 = {CorsFilter@7653}
 * 6 = {CsrfFilter@7652}
 * 7 = {OidcLogoutEndpointFilter@7651}
 * 8 = {LogoutFilter@7650}
 * 9 = {OAuth2AuthorizationServerMetadataEndpointFilter@7649}
 * 10 = {OAuth2AuthorizationEndpointFilter@7648}                // Or [Or [Ant [pattern='/oauth2/authorize', GET], And [Ant [pattern='/oauth2/authorize', POST], org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter$$Lambda/0x00000283aa493f40@3ceb8414, org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter$$Lambda/0x00000283aa493d18@38bb586d]], And [Ant [pattern='/oauth2/authorize', POST], Not [org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter$$Lambda/0x00000283aa493f40@3ceb8414]]]
 * 11 = {OAuth2DeviceVerificationEndpointFilter@7647}
 * 12 = {OidcProviderConfigurationEndpointFilter@7646}
 * 13 = {NimbusJwkSetEndpointFilter@7645}
 * 14 = {OAuth2ClientAuthenticationFilter@7644}
 * 15 = {BearerTokenAuthenticationFilter@7643}
 * 16 = {RequestCacheAwareFilter@7642}
 * 17 = {SecurityContextHolderAwareRequestFilter@7641}
 * 18 = {AnonymousAuthenticationFilter@7640}
 * 19 = {ExceptionTranslationFilter@7623}
 * 20 = {AuthorizationFilter@9241}
 * 21 = {OAuth2TokenEndpointFilter@9242}                        // Ant [pattern='/oauth2/token', POST]
 * 22 = {OAuth2TokenIntrospectionEndpointFilter@9243}
 * 23 = {OAuth2TokenRevocationEndpointFilter@9244}
 * 24 = {OAuth2DeviceAuthorizationEndpointFilter@9245}
 * 25 = {OidcUserInfoEndpointFilter@9246}
 *
 */
@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {

    @Bean
    public PasswordEncoder passwordEncoder() throws NoSuchAlgorithmException {
        return new BCryptPasswordEncoder(12,SecureRandom.getInstanceStrong());
    }

    /**
     * 提供登录页面用户名密码的认证
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwdEncoder) {
        UserDetails user= User.builder()
                .username("user")
                //.password(passwdEncoder.encode("123")) // 使用 {noop} 前缀表示密码不会被编码
                .password("{noop}123") // 使用 {noop} 前缀表示密码不会被编码
                .accountExpired(false)
                .credentialsExpired(false)
                .accountLocked(false)
                .authorities("ROLE_USER") // 用户的权限
                .build();

        return new InMemoryUserDetailsManager(user);
    }

    /**
     * 应用注册仓库
     * @return
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwdEncoder) {
        RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("clientid")
                .clientSecret(passwdEncoder.encode("client_secret"))////如果是CLIENT_SECRET_POST才会用到
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .redirectUri("http://localhost:8080/login/oauth2/code/clientid")
                .postLogoutRedirectUri("http://localhost:8080/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .build();

        return new InMemoryRegisteredClientRepository(oidcClient);
    }

    /**
     * 在 JSON Web Token（JWT）中，常见的参数包括：
     * iss（Issuer）：签发者，表示生成此 JWT 的实体。例如，一个服务的 URL 或特定的标识。
     * 例子：iss: "https://my-auth-server.com" ，表示此令牌由 https://my-auth-server.com 签发。
     * sub（Subject）：主题，通常是此令牌所针对的用户、客户端或实体的唯一标识符。
     * 比如：sub: "user123" ，标识此令牌与用户 user123 相关。
     * aud（Audience）：受众，表示此 JWT 预期的接收者或使用方。
     * 例如：aud: ["client-app1", "client-app2"] ，表示此令牌可被 client-app1 和 client-app2 两个应用使用。
     * exp（Expiration Time）：过期时间，是一个 Unix 时间戳，表示此 JWT 失效的时间。
     * 比如：exp: 1693571200 ，表示在该时间戳所代表的时间点，此令牌过期。
     * nbf（Not Before）：生效时间，也是一个 Unix 时间戳，表示此 JWT 在指定时间之前不可用。
     * 例如：nbf: 1693567600 ，表示在该时间戳之前，此令牌不可用。
     * iat（Issued At）：签发时间，是一个 Unix 时间戳，表示此 JWT 的生成时间。
     * 比如：iat: 1693564000 ，表示此令牌在该时间戳所代表的时间生成。
     * jti（JWT ID）：JWT 标识符，用于标识此 JWT 的唯一性。
     * 例如：jti: "token123" ，为这个 JWT 提供了一个唯一的标识。
     * 这些参数共同为 JWT 提供了身份验证、授权和有效性的相关信息。
     *
     *
     * JWT 通常分为三部分：
     *
     * 头部（Header）
     * 这部分通常包含有关令牌的类型（即 JWT）和所使用的加密算法（如 HS256、RS256 等）的信息。
     * 它是以 JSON 格式进行编码，然后进行 Base64Url 编码。
     *
     * 例如：
     * json
     * 复制
     *     {
     *       "alg": "HS256",
     *       "typ": "JWT"
     *     }
     * 经过 Base64Url 编码后可能类似于：eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
     *
     * 载荷（Payload）
     * 这部分包含了有关声明（Claims）的信息，例如用户身份、权限、令牌的有效期等。
     * 同样以 JSON 格式编码，然后进行 Base64Url 编码。
     *
     * 例如：
     * json
     * 复制
     *     {
     *       "sub": "1234567890",
     *       "name": "John Doe",
     *       "iat": 1516239022
     *     }
     * 经过 Base64Url 编码后可能类似于：eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
     *
     * 签名（Signature）
     * 这部分是通过对头部和载荷进行特定的加密运算生成的。
     * 加密运算使用在头部中指定的算法，并使用一个密钥。
     * 其目的是验证令牌在传输过程中没有被篡改，并且确实是由可信的签发者生成的。
     *
     * 例如，如果使用 HS256 算法和密钥 secretKey 对前面的头部和载荷进行签名，生成的签名可能类似于：signature123456789
     * 这三个部分通过 . 连接，形成一个完整的 JWT 字符串，例如：eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.signature123456789
     */

    /**
     * 生成jwk,用在jwt编码和jwt解码器上
     * @return
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * 生成RSA256非对称的秘钥对：公钥和私钥，其中公钥会出布出去。
     * @return
     */
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * jwt 解码器，给资源服务器用
     * @param jwkSource
     * @return
     */
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * jwt 编码器，给授权服务器用
     * @param jwkSource
     * @return
     */
    @Bean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        return new NimbusJwtEncoder(jwkSource);
    }

    /**
     * 默认授权服务器配置
     * @return
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }
}
